
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       mb_arming.c
  * @author     baiyang
  * @date       2022-11-17
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include "mb_arming.h"

#include <stdio.h>
#include <stdlib.h>

#include <rtconfig.h>

#if defined(HAL_WITH_IO_MCU)
#include <iomcu/gp_iomcu.h>
#endif

#if CONFIG_HAL_BOARD != HAL_BOARD_SITL_WIN
#include <sensor_imu/sensor_imu.h>
#include <sensor_gps/sensor_gps.h>
#include <sensor_baro/sensor_baro.h>
#include <sensor_compass/sensor_compass.h>
#endif

#include <uITC/uITC.h>
#include <logger/blog.h>
#include <logger/ap_log.h>
#include <uITC/uITC_msg.h>
#include <notify/notify.h>
#include <ahrs/ahrs_view.h>
#include <parameter/param.h>
#include <gcs_mavlink/gcs.h>
#include <mission/mb_mission.h>
#include <common/time/gp_time.h>
#include <rc_mapper/rc_mapper.h>
#include <vehicle/vehicle_type.h>
#include <rc_channel/rc_channel.h>
#include <srv_channel/srv_channel.h>
#include <board_config/board_config.h>
#include <common/gp_math/gp_mathlib.h>
#include <battery_monitor/battery_monitor.h>
/*-----------------------------------macro------------------------------------*/
#define AP_ARMING_COMPASS_MAGFIELD_EXPECTED 530
#define AP_ARMING_COMPASS_MAGFIELD_MIN  185     // 0.35 * 530 milligauss
#define AP_ARMING_COMPASS_MAGFIELD_MAX  875     // 1.65 * 530 milligauss
#define AP_ARMING_BOARD_VOLTAGE_MAX     5.8f
#define AP_ARMING_ACCEL_ERROR_THRESHOLD 0.75f
#define AP_ARMING_AHRS_GPS_ERROR_MAX    10      // accept up to 10m difference between AHRS and GPS

#if MB_BUILD_TYPE(MICROBEE_BUILD_Plane)
  #define ARMING_RUDDER_DEFAULT         (uint8_t)ARMING_RUDDER_ARMONLY
#else
  #define ARMING_RUDDER_DEFAULT         (uint8_t)ARMING_RUDDER_ARMDISARM
#endif

#ifndef PREARM_DISPLAY_PERIOD
# define PREARM_DISPLAY_PERIOD 30
#endif

/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/

/*----------------------------------variable----------------------------------*/
static param_t var_info[] = {
    PARAM_DEFINE_INT8(ARMING_REQUIRE, ARMING_REQUIRED_YES_MIN_PWM),
    PARAM_DEFINE_INT32(ARMING_CHECK, ARMING_CHECK_ALL),
    PARAM_DEFINE_FLOAT(ARMING_ACCTHRESH,AP_ARMING_ACCEL_ERROR_THRESHOLD),
    PARAM_DEFINE_INT8(ARMING_RUDDER, ARMING_RUDDER_DEFAULT),
    PARAM_DEFINE_INT32(ARMING_MIS_ITEMS, 0),
    PARAM_DEFINE_INT32(ARMING_OPTIONS, 0),
};
PARAM_GROUP_DEFINE(ARMING, var_info);

static mb_arming_t _singleton;
/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
void mb_arming_ctor(mb_arming_t arming, mb_arming_ops_t ops)
{
    _singleton = arming;

    _singleton->ops = ops;

    _singleton->_last_disarm_method = ARMING_METHOD_UNKNOWN;
}

// get singleton instance
mb_arming_t mb_arming_get_singleton()
{
    return _singleton;
}

void mb_arming_init()
{
    param_link_variable(PARAM_ID(ARMING, ARMING_REQUIRE), &_singleton->require);
    param_link_variable(PARAM_ID(ARMING, ARMING_CHECK), &_singleton->checks_to_perform);
    param_link_variable(PARAM_ID(ARMING, ARMING_ACCTHRESH), &_singleton->accel_error_threshold);
    param_link_variable(PARAM_ID(ARMING, ARMING_RUDDER), &_singleton->_rudder_arming);
    param_link_variable(PARAM_ID(ARMING, ARMING_MIS_ITEMS), &_singleton->_required_mission_items);
    param_link_variable(PARAM_ID(ARMING, ARMING_OPTIONS), &_singleton->_arming_options);
}

// performs pre-arm checks. expects to be called at 1hz.
void mb_arming_update(void)
{
    const uint32_t now_ms = time_millis();

    // perform pre-arm checks & display failures every 30 seconds
    bool display_fail = false;
    if (now_ms - _singleton->last_prearm_display_ms > PREARM_DISPLAY_PERIOD*1000) {
        display_fail = true;
        _singleton->last_prearm_display_ms = now_ms;
    }

    // OTOH, the user may never want to display them:
    if ((_singleton->_arming_options & ARMING_OPTION_DISABLE_PREARM_DISPLAY) != 0) {
        display_fail = false;
    }

    mb_arming_pre_arm_checks(display_fail);
}

enum ArmingRequired mb_arming_required()
{
#if AP_OPENDRONEID_ENABLED
    // cannot be disabled if OpenDroneID is present
    if (AP::opendroneid().enabled()) {
        if (require != Required::YES_MIN_PWM && require != Required::YES_ZERO_PWM) {
            return Required::YES_MIN_PWM;
        }
    }
#endif
    return (enum ArmingRequired)_singleton->require;
}

bool mb_arming_rc_in_calibration_check(bool report)
{
    if (rcs_get_calibrating()) {
        mb_arming_check_failed2(ARMING_CHECK_RC, report, "RC calibrating");
        return false;
    }
    return true;
}

void mb_arming_check_failed(bool report, const char *fmt, ...)
{
    if (!report) {
        return;
    }
    char taggedfmt[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1];

    // metafmt is wrapped around the passed-in format string to
    // prepend "PreArm" or "Arm", depending on what sorts of checks
    // we're currently doing.
    const char *metafmt = "PreArm: %s";  // it's formats all the way down
    if (_singleton->running_arming_checks) {
        metafmt = "Arm: %s";
    }
    snprintf(taggedfmt, sizeof(taggedfmt), metafmt, fmt);

    va_list arg_list;
    va_start(arg_list, fmt);
    gcs_send_textv(MAV_SEVERITY_CRITICAL, taggedfmt, arg_list);
    va_end(arg_list);
}

void mb_arming_check_failed2(const enum ArmingChecks check, bool report, const char *fmt, ...)
{
    if (!report) {
        return;
    }
    char taggedfmt[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1];

    // metafmt is wrapped around the passed-in format string to
    // prepend "PreArm" or "Arm", depending on what sorts of checks
    // we're currently doing.
    const char *metafmt = "PreArm: %s";  // it's formats all the way down
    if (_singleton->running_arming_checks) {
        metafmt = "Arm: %s";
    }
    snprintf(taggedfmt, sizeof(taggedfmt), metafmt, fmt);

    MAV_SEVERITY severity = MAV_SEVERITY_CRITICAL;
    if (!mb_arming_check_enabled(check)) {
        // technically should be NOTICE, but will annoy users at that level:
        severity = MAV_SEVERITY_DEBUG;
    }
    va_list arg_list;
    va_start(arg_list, fmt);
    gcs_send_textv(severity, taggedfmt, arg_list);
    va_end(arg_list);
}

bool mb_arming_is_armed()
{
    return _singleton->armed || mb_arming_required() == ARMING_REQUIRED_NO;
}

/*
  true if armed and safety is off
 */
bool mb_arming_is_armed_and_safety_off()
{
    return mb_arming_is_armed() && brd_safety_switch_state() != SAFETY_DISARMED;
}

uint32_t mb_arming_get_enabled_checks()
{
    return _singleton->checks_to_perform;
}

bool mb_arming_check_enabled(const enum ArmingChecks check)
{
    if (_singleton->checks_to_perform & ARMING_CHECK_ALL) {
        return true;
    }
    return (_singleton->checks_to_perform & check);
}

bool mb_arming_rc_arm_checks(enum ArmingMethod method)
{
    // don't check the trims if we are in a failsafe
    if (!rcs_has_valid_input()) {
        return true;
    }

    // only check if we've received some form of input within the last second
    // this is a protection against a vehicle having never enabled an input
    uint32_t last_input_ms = rcs_last_input_ms();
    if ((last_input_ms == 0) || ((time_millis() - last_input_ms) > 1000)) {
        return true;
    }

    bool check_passed = true;
    // ensure all rc channels have different functions
    if (rcs_duplicate_options_exist()) {
        mb_arming_check_failed2(ARMING_CHECK_PARAMETERS, true, "Duplicate Aux Switch Options");
        check_passed = false;
    }
    if (rcs_flight_mode_channel_conflicts_with_rc_option()) {
        mb_arming_check_failed2(ARMING_CHECK_PARAMETERS, true, "Mode channel and RC%d_OPTION conflict", rcs_flight_mode_channel_number());
        check_passed = false;
    }

    const rc_mapper_t rcmap = rc_mapper_get_singleton();
    if (rcmap != NULL) {
        if (!rcs_arming_skip_checks_rpy()) {
            const char *names[3] = {"Roll", "Pitch", "Yaw"};
            const uint8_t channels[3] = {rcmap->ch_roll, rcmap->ch_pitch, rcmap->ch_yaw};
            for (uint8_t i = 0; i < ARRAY_SIZE(channels); i++) {
                rc_channel_const_t c = rcs_chan(channels[i] - 1);
                if (c == NULL) {
                    continue;
                }
                if (rc_get_control_in(c) != 0) {
                    if ((method != ARMING_METHOD_RUDDER) || (c != rcs_get_arming_channel())) { // ignore the yaw input channel if rudder arming
                        mb_arming_check_failed2(ARMING_CHECK_RC, true, "%s (RC%d) is not neutral", names[i], channels[i]);
                        check_passed = false;
                    }
                }
            }
        }

        // if throttle check is enabled, require zero input
        if (rcs_arming_check_throttle()) {
            rc_channel_t c = rcs_chan(rcmap->ch_throttle - 1);
            if (c != NULL) {
                if (rc_get_control_in(c) != 0) {
                    mb_arming_check_failed2(ARMING_CHECK_RC, true, "Throttle (RC%d) is not neutral", rcmap->ch_throttle);
                    check_passed = false;
                }
            }
            c = rcs_find_channel_for_option(RC_AUX_FWD_THR);
            if (c != NULL) {
                uint8_t fwd_thr = rc_percent_input(c);
                // require channel input within 2% of minimum
                if (fwd_thr > 2) {
                    mb_arming_check_failed2(ARMING_CHECK_RC, true, "VTOL Fwd Throttle is not zero");
                    check_passed = false;
                }
            }
        }
    }
    return check_passed;
}

bool mb_arming_ins_accels_consistent()
{
#if CONFIG_HAL_BOARD == HAL_BOARD_RTTHREAD
    const uint8_t accel_count = sensor_imu_get_accel_count();
    if (accel_count <= 1) {
        return true;
    }

    const Vector3f_t prime_accel_vec = sensor_imu_get_accel();
    const uint32_t now = time_millis();
    for(uint8_t i=0; i<accel_count; i++) {
        if (!sensor_imu_use_accel(i)) {
            continue;
        }
        // get next accel vector
        const Vector3f_t accel_vec = sensor_imu_get_accel2(i);

        Vector3f_t vec_diff;
        vec3_sub(&vec_diff, &accel_vec, &prime_accel_vec);
        // allow for user-defined difference, typically 0.75 m/s/s. Has to pass in last 10 seconds
        float threshold = _singleton->accel_error_threshold;
        if (i >= 2) {
            /*
              we allow for a higher threshold for IMU3 as it
              runs at a different temperature to IMU1/IMU2,
              and is not used for accel data in the EKF
            */
            threshold *= 3;
        }

        // EKF is less sensitive to Z-axis error
        vec_diff.z *= 0.5f;

        if (vec3_length(&vec_diff) > threshold) {
            // this sensor disagrees with the primary sensor, so
            // accels are inconsistent:
            return false;
        }
    }

    // we didn't return false in the loop above, so sensors are
    // consistent right now:
    _singleton->last_accel_pass_ms = now;

    // must pass for at least 10 seconds before we're considered consistent:
    if (now - _singleton->last_accel_pass_ms > 10000) {
        return false;
    }
#endif

    return true;
}

bool mb_arming_ins_gyros_consistent()
{
#if CONFIG_HAL_BOARD == HAL_BOARD_RTTHREAD
    const uint8_t gyro_count = sensor_imu_get_gyro_count();
    if (gyro_count <= 1) {
        return true;
    }

    const Vector3f_t prime_gyro_vec = sensor_imu_get_gyro();
    const uint32_t now = time_millis();
    for(uint8_t i=0; i<gyro_count; i++) {
        if (!sensor_imu_use_gyro(i)) {
            continue;
        }
        // get next gyro vector
        const Vector3f_t gyro_vec = sensor_imu_get_gyro2(i);

        Vector3f_t vec_diff;
        vec3_sub(&vec_diff, &gyro_vec, &prime_gyro_vec);

        // allow for up to 5 degrees/s difference
        if (vec3_length(&vec_diff) > radians(5)) {
            // this sensor disagrees with the primary sensor, so
            // gyros are inconsistent:
            return false;
        }
    }

    // we didn't return false in the loop above, so sensors are
    // consistent right now:
    _singleton->last_gyro_pass_ms = now;

    // must pass for at least 10 seconds before we're considered consistent:
    if (now - _singleton->last_gyro_pass_ms > 10000) {
        return false;
    }
#endif

    return true;
}

// Copter and sub share the same RC input limits
// Copter checks that min and max have been configured by default, Sub does not
bool mb_arming_rc_checks_copter_sub(const bool display_failure, rc_channel_const_t channels[4])
{
    // set rc-checks to success if RC checks are disabled
    if ((_singleton->checks_to_perform != ARMING_CHECK_ALL) && !(_singleton->checks_to_perform & ARMING_CHECK_RC)) {
        return true;
    }

    bool ret = true;

    const char *channel_names[] = { "Roll", "Pitch", "Throttle", "Yaw" };

    for (uint8_t i=0; i<ARRAY_SIZE(channel_names);i++) {
        rc_channel_const_t channel = channels[i];
        const char *channel_name = channel_names[i];
        // check if radio has been calibrated
        if (rc_get_radio_min(channel) > RC_CALIB_MIN_LIMIT_PWM) {
            mb_arming_check_failed2(ARMING_CHECK_RC, display_failure, "%s radio min too high", channel_name);
            ret = false;
        }
        if (rc_get_radio_max(channel) < RC_CALIB_MAX_LIMIT_PWM) {
            mb_arming_check_failed2(ARMING_CHECK_RC, display_failure, "%s radio max too low", channel_name);
            ret = false;
        }
        bool fail = true;
        if (i == 2) {
            // skip checking trim for throttle as older code did not check it
            fail = false;
        }
        if (rc_get_radio_trim(channel) < rc_get_radio_min(channel)) {
            mb_arming_check_failed2(ARMING_CHECK_RC, display_failure, "%s radio trim below min", channel_name);
            if (fail) {
                ret = false;
            }
        }
        if (rc_get_radio_trim(channel) > rc_get_radio_max(channel)) {
            mb_arming_check_failed2(ARMING_CHECK_RC, display_failure, "%s radio trim above max", channel_name);
            if (fail) {
                ret = false;
            }
        }
    }
    return ret;
}

// check disarm switch is asserted
bool mb_arming_disarm_switch_checks(bool display_failure)
{
    rc_channel_const_t chan = rcs_find_channel_for_option(RC_AUX_DISARM);
    if (chan != NULL &&
        rc_get_aux_switch_pos(chan) == RC_AUX_SWITCH_HIGH) {
        mb_arming_check_failed(display_failure, "Disarm Switch on");
        return false;
    }

    return true;
}

bool mb_arming_hardware_safety_check(bool report)
{
    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) ||
        (_singleton->checks_to_perform & ARMING_CHECK_SWITCH)) {

      // check if safety switch has been pushed
      if (brd_safety_switch_state() == SAFETY_DISARMED) {
          mb_arming_check_failed2(ARMING_CHECK_SWITCH, report, "Hardware safety switch");
          return false;
      }
    }

    return true;
}

bool mb_arming_compass_checks(bool report)
{
#if CONFIG_HAL_BOARD == HAL_BOARD_RTTHREAD
#if COMPASS_CAL_ENABLED
    // check if compass is calibrating
    if (sensor_compass_is_calibrating()) {
        mb_arming_check_failed(report, "Compass calibration running");
        return false;
    }

    // check if compass has calibrated and requires reboot
    if (sensor_compass_cal_requires_reboot()) {
        mb_arming_check_failed(report, "Compass calibrated requires reboot");
        return false;
    }
#endif

    if ((_singleton->checks_to_perform) & ARMING_CHECK_ALL ||
        (_singleton->checks_to_perform) & ARMING_CHECK_COMPASS) {

        // avoid Compass::use_for_yaw(void) as it implicitly calls healthy() which can
        // incorrectly skip the remaining checks, pass the primary instance directly
        if (!sensor_compass_use_for_yaw2(0)) {
            // compass use is disabled
            return true;
        }

        if (!sensor_compass_healthy()) {
            mb_arming_check_failed2(ARMING_CHECK_COMPASS, report, "Compass not healthy");
            return false;
        }
        // check compass learning is on or offsets have been set
#if !MB_BUILD_COPTER_OR_HELI && !MB_BUILD_TYPE(MICROBEE_BUILD_Blimp)
        // check compass offsets have been set if learning is off
        // copter and blimp always require configured compasses
        if (!sensor_compass_learn_offsets_enabled())
#endif
        {
            char failure_msg[50] = {0};
            if (!sensor_compass_configured2(failure_msg, ARRAY_SIZE(failure_msg))) {
                mb_arming_check_failed2(ARMING_CHECK_COMPASS, report, "%s", failure_msg);
                return false;
            }
        }

        // check for unreasonable compass offsets
        const Vector3f_t *offsets = sensor_compass_get_offsets();
        if (vec3_length(offsets) > sensor_compass_get_offsets_max()) {
            mb_arming_check_failed2(ARMING_CHECK_COMPASS, report, "Compass offsets too high");
            return false;
        }

        // check for unreasonable mag field length
        const float mag_field = vec3_length(sensor_compass_get_field());
        if (mag_field > AP_ARMING_COMPASS_MAGFIELD_MAX || mag_field < AP_ARMING_COMPASS_MAGFIELD_MIN) {
            mb_arming_check_failed2(ARMING_CHECK_COMPASS, report, "Check mag field: %4.0f, max %d, min %d", (double)mag_field, AP_ARMING_COMPASS_MAGFIELD_MAX, AP_ARMING_COMPASS_MAGFIELD_MIN);
            return false;
        }

        // check all compasses point in roughly same direction
        if (!sensor_compass_consistent()) {
            mb_arming_check_failed2(ARMING_CHECK_COMPASS, report, "Compasses inconsistent");
            return false;
        }
    }
#endif

    return true;
}

bool mb_arming_airspeed_checks(bool report)
{
#if 0
    if (mb_arming_check_enabled(ARMING_CHECK_AIRSPEED)) {
        const AP_Airspeed *airspeed = AP_Airspeed::get_singleton();
        if (airspeed == nullptr) {
            // not an airspeed capable vehicle
            return true;
        }
        for (uint8_t i=0; i<AIRSPEED_MAX_SENSORS; i++) {
            if (airspeed->enabled(i) && airspeed->use(i) && !airspeed->healthy(i)) {
                check_failed(ARMING_CHECK_AIRSPEED, report, "Airspeed %d not healthy", i + 1);
                return false;
            }
        }
    }
#endif

    return true;
}

bool mb_arming_battery_checks(bool report)
{
    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) ||
        (_singleton->checks_to_perform & ARMING_CHECK_BATTERY)) {

        char buffer[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1] = {0};
        if (!battmonitor_arming_checks(sizeof(buffer), buffer)) {
            mb_arming_check_failed2(ARMING_CHECK_BATTERY, report, "%s", buffer);
            return false;
        }
     }
    return true;
}

bool mb_arming_manual_transmitter_checks(bool report)
{
    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) ||
        (_singleton->checks_to_perform & ARMING_CHECK_RC)) {

        if (notify_flags.failsafe_radio) {
            mb_arming_check_failed2(ARMING_CHECK_RC, report, "Radio failsafe on");
            return false;
        }

        if (!mb_arming_rc_calibration_checks(report)) {
            return false;
        }
    }

    return mb_arming_rc_in_calibration_check(report);
}

bool mb_arming_servo_checks(bool report)
{
#if NUM_SERVO_CHANNELS
    srv_channels *srvs = srv_channels_get_singleton();

    bool check_passed = true;
    for (uint8_t i = 0; i < NUM_SERVO_CHANNELS; i++) {
        const srv_channel *c = &srvs->channels[i];
        if (c == NULL || srv_channel_get_function(c) <= k_none) {
            continue;
        }

        const uint16_t trim = srv_channel_get_trim(c);
        if (srv_channel_get_output_min(c) > trim) {
            mb_arming_check_failed(report, "SERVO%d_MIN is greater than SERVO%d_TRIM", i + 1, i + 1);
            check_passed = false;
        }
        if (srv_channel_get_output_max(c) < trim) {
            mb_arming_check_failed(report, "SERVO%d_MAX is less than SERVO%d_TRIM", i + 1, i + 1);
            check_passed = false;
        }

        // check functions using PWM are enabled
        if (srvs->disabled_mask & 1U<<i) {
            const Aux_servo_function_t ch_function = srv_channel_get_function(c);

            // motors, e-stoppable functions, neopixels and ProfiLEDs may be digital outputs and thus can be disabled
            const bool disabled_ok = srv_channel_is_motor(ch_function) ||
                                     srv_channel_should_e_stop(ch_function) ||
                                     (ch_function >= k_LED_neopixel1 && ch_function <= k_LED_neopixel4) ||
                                     (ch_function >= k_ProfiLED_1 && ch_function <= k_ProfiLED_Clock);

            // for all other functions raise a pre-arm failure
            if (!disabled_ok) {
                mb_arming_check_failed(report, "SERVO%u_FUNCTION=%u on disabled channel", i + 1, (unsigned)ch_function);
                check_passed = false;
            }
        }
    }

#if defined(HAL_WITH_IO_MCU)
    if (!iomcu_healthy()) {
        mb_arming_check_failed(report, "IOMCU is unhealthy");
        check_passed = false;
    }
#endif

    return check_passed;
#else
    return false;
#endif
}

enum RudderArming mb_arming_get_rudder_arming_type()
{
    return (enum RudderArming)_singleton->_rudder_arming;
}


/// 
//returns true if arming occurred successfully
bool mb_armingArm(enum ArmingMethod method, const bool do_arming_checks)
{
    if (_singleton->armed) { //already armed
        return false;
    }

    _singleton->running_arming_checks = true;  // so we show Arm: rather than Disarm: in messages

    if ((!do_arming_checks && mb_arming_mandatory_checks(true)) || (mb_arming_pre_arm_checks(true) && mb_arming_arm_checks(method))) {
        _singleton->armed = true;

        //Log_Write_Arm(!do_arming_checks, method); // note Log_Write_Armed takes forced not do_arming_checks

    } else {
        //AP::logger().arming_failure();
        _singleton->armed = false;
    }

    _singleton->running_arming_checks = false;

    if (_singleton->armed && do_arming_checks && _singleton->checks_to_perform == 0) {
        gcs_send_text(MAV_SEVERITY_WARNING, "Warning: Arming Checks Disabled");
    }

#if 0
#if HAL_GYROFFT_ENABLED
    // make sure the FFT subsystem is enabled if arming checks have been disabled
    AP_GyroFFT *fft = AP::fft();
    if (fft != nullptr) {
        fft->prepare_for_arming();
    }
#endif

#if AP_TERRAIN_AVAILABLE
    if (armed) {
        // tell terrain we have just armed, so it can setup
        // a reference location for terrain adjustment
        auto *terrain = AP::terrain();
        if (terrain != nullptr) {
            terrain->set_reference_location();
        }
    }
#endif
#endif

    return _singleton->armed;
}

//returns true if disarming occurred successfully
bool mb_armingDisarm(const enum ArmingMethod method, bool do_disarm_checks)
{
    if (!_singleton->armed) { // already disarmed
        return false;
    }
    _singleton->armed = false;
    _singleton->_last_disarm_method = method;

    //Log_Write_Disarm(!do_disarm_checks, method);  // Log_Write_Disarm takes "force"

    //check_forced_logging(method);

#if HAL_HAVE_SAFETY_SWITCH
    if ((brd_get_safety_button_options() & BOARD_SAFETY_OPTION_SAFETY_ON_DISARM)) {
        srv_hal_force_safety_on();
    }
#endif // HAL_HAVE_SAFETY_SWITCH

#if 0

#if HAL_GYROFFT_ENABLED
    AP_GyroFFT *fft = AP::fft();
    if (fft != nullptr) {
        fft->save_params_on_disarm();
    }
#endif

#if AP_FENCE_ENABLED
    AC_Fence *fence = AP::fence();
    if (fence != nullptr) {
        if(fence->auto_enabled() == AC_Fence::AutoEnable::ONLY_WHEN_ARMED) {
            fence->enable(false);
        }
    }
#endif
#endif

    return true;
}

bool mb_arming_ArmChecks(enum ArmingMethod method)
{
    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) ||
        (_singleton->checks_to_perform & ARMING_CHECK_RC)) {
        if (!mb_arming_rc_arm_checks(method)) {
            return false;
        }
    }

#if CONFIG_HAL_BOARD != HAL_BOARD_SITL_WIN
    // ensure the GPS drivers are ready on any final changes
    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) ||
        (_singleton->checks_to_perform & ARMING_CHECK_GPS_CONFIG)) {
        if (!sensor_gps_prepare_for_arming()) {
            return false;
        }
    }
#endif

#if 0
#if AP_FENCE_ENABLED
    AC_Fence *fence = AP::fence();
    if (fence != nullptr) {
        // If a fence is set to auto-enable, turn on the fence
        if(fence->auto_enabled() == AC_Fence::AutoEnable::ONLY_WHEN_ARMED) {
            fence->enable(true);
        }
    }
#endif

    // note that this will prepare AP_Logger to start logging
    // so should be the last check to be done before arming

    // Note also that we need to PrepForArming() regardless of whether
    // the arming check flag is set - disabling the arming check
    // should not stop logging from working.

    AP_Logger *logger = AP_Logger::get_singleton();
    if (logger->logging_present()) {
        // If we're configured to log, prep it
        logger->PrepForArming();
        if (!logger->logging_started() &&
            ((checks_to_perform & ARMING_CHECK_ALL) ||
             (checks_to_perform & ARMING_CHECK_LOGGING))) {
            check_failed(ARMING_CHECK_LOGGING, true, "Logging not started");
            return false;
        }
    }
#endif

    return true;
}

bool mb_arming_MandatoryChecks(bool report)
{
    bool ret = true;
#if AP_OPENDRONEID_ENABLED
    ret &= opendroneid_checks(report);
#endif
    ret &= mb_arming_rc_in_calibration_check(report);
    return ret;
}

bool mb_arming_PreArmChecks(bool report)
{
#if !MB_BUILD_COPTER_OR_HELI
    if (_singleton->armed || mb_arming_required() == ARMING_REQUIRED_NO) {
        // if we are already armed or don't need any arming checks
        // then skip the checks
        return true;
    }
#endif

    return mb_arming_hardware_safety_check(report)
        &  mb_arming_barometer_checks(report)
        &  mb_arming_ins_checks(report)
        &  mb_arming_compass_checks(report)
        &  mb_arming_gps_checks(report)
        &  mb_arming_battery_checks(report)
        //&  logging_checks(report)
        &  mb_arming_manual_transmitter_checks(report)
        &  mb_arming_mission_checks(report)
        //&  rangefinder_checks(report)
        &  mb_arming_servo_checks(report)
        &  mb_arming_board_voltage_checks(report)
        &  mb_arming_system_checks(report)
        //&  terrain_checks(report)
        //&  can_checks(report)
        //&  mb_arming_generator_checks(report)
        &  mb_arming_proximity_checks(report)
        //&  mb_arming_camera_checks(report)
        //&  mb_arming_osd_checks(report)
        //&  mb_arming_mount_checks(report)
        //&  fettec_checks(report)
        //&  visodom_checks(report)
        //&  aux_auth_checks(report)
        &  mb_arming_disarm_switch_checks(report)
        //&  fence_checks(report)
        //&  opendroneid_checks(report);
        ;
}

bool mb_arming_BarometerChecks(bool report)
{
#if CONFIG_HAL_BOARD == HAL_BOARD_RTTHREAD
    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) ||
        (_singleton->checks_to_perform & ARMING_CHECK_BARO)) {
        char buffer[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1] = {0};
        if (!sensor_baro_arming_checks(sizeof(buffer), buffer)) {
            mb_arming_check_failed2(ARMING_CHECK_BARO, report, "Baro: %s", buffer);
            return false;
        }
    }
#endif

    return true;
}

bool mb_arming_InsChecks(bool report)
{
#if CONFIG_HAL_BOARD == HAL_BOARD_RTTHREAD
    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) ||
        (_singleton->checks_to_perform & ARMING_CHECK_INS)) {
        if (!sensor_imu_get_gyro_health_all()) {
            mb_arming_check_failed2(ARMING_CHECK_INS, report, "Gyros not healthy");
            return false;
        }
        if (!sensor_imu_gyro_calibrated_ok_all()) {
            mb_arming_check_failed2(ARMING_CHECK_INS, report, "Gyros not calibrated");
            return false;
        }
        if (!sensor_imu_get_accel_health_all()) {
            mb_arming_check_failed2(ARMING_CHECK_INS, report, "Accels not healthy");
            return false;
        }
        if (!sensor_imu_accel_calibrated_ok_all()) {
            mb_arming_check_failed2(ARMING_CHECK_INS, report, "3D Accel calibration needed");
            return false;
        }
        
        //check if accelerometers have calibrated and require reboot
        if (sensor_imu_accel_cal_requires_reboot()) {
            mb_arming_check_failed2(ARMING_CHECK_INS, report, "Accels calibrated requires reboot");
            return false;
        }

        // check all accelerometers point in roughly same direction
        if (!mb_arming_ins_accels_consistent()) {
            mb_arming_check_failed2(ARMING_CHECK_INS, report, "Accels inconsistent");
            return false;
        }

        // check all gyros are giving consistent readings
        if (!mb_arming_ins_gyros_consistent()) {
            mb_arming_check_failed2(ARMING_CHECK_INS, report, "Gyros inconsistent");
            return false;
        }

        // no arming while doing temp cal
        if (sensor_imu_temperature_cal_running()) {
            mb_arming_check_failed2(ARMING_CHECK_INS, report, "temperature cal running");
            return false;
        }
    }

#if 0
#if HAL_GYROFFT_ENABLED
    // gyros are healthy so check the FFT
    if ((checks_to_perform & ARMING_CHECK_ALL) ||
        (checks_to_perform & ARMING_CHECK_FFT)) {
        // Check that the noise analyser works
        AP_GyroFFT *fft = AP::fft();

        char fail_msg[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1];
        if (fft != nullptr && !fft->pre_arm_check(fail_msg, ARRAY_SIZE(fail_msg))) {
            check_failed(ARMING_CHECK_INS, report, "%s", fail_msg);
            return false;
        }
    }
#endif
#endif
#endif

    return true;
}

bool mb_arming_GpsChecks(bool report)
{
#if CONFIG_HAL_BOARD == HAL_BOARD_RTTHREAD
    const sensor_gps* gps = sensor_gps_get_singleton();

    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) || (_singleton->checks_to_perform & ARMING_CHECK_GPS)) {

        // Any failure messages from GPS backends
            char failure_msg[50] = {0};
            if (!sensor_gps_backends_healthy(failure_msg, ARRAY_SIZE(failure_msg))) {
                if (failure_msg[0] != '\0') {
                    mb_arming_check_failed2(ARMING_CHECK_GPS, report, "%s", failure_msg);
                }
                return false;
        }

        for (uint8_t i = 0; i < sensor_gps_num_sensors(); i++) {
#if defined(GPS_BLENDED_INSTANCE)
            if ((i != GPS_BLENDED_INSTANCE) &&
#else
            if (
#endif
                    (sensor_gps_get_type(i) == GPS_TYPE_NONE)) {
                if (gps->primary_instance == i) {
                    mb_arming_check_failed2(ARMING_CHECK_GPS, report, "GPS %i: primary but TYPE 0", i+1);
                    return false;
                }
                continue;
            }

            //GPS OK?
            if (sensor_gps_status(i) < GPS_OK_FIX_3D) {
                mb_arming_check_failed2(ARMING_CHECK_GPS, report, "GPS %i: Bad fix", i+1);
                return false;
            }

            //GPS update rate acceptable
            if (!sensor_gps_is_healthy2(i)) {
                mb_arming_check_failed2(ARMING_CHECK_GPS, report, "GPS %i: not healthy", i+1);
                return false;
            }
        }

        ahrs_view* ahrs = get_ahrs_view();
        if (!ahrs_home_is_set(ahrs)) {
            mb_arming_check_failed2(ARMING_CHECK_GPS, report, "GPS: waiting for home");
            return false;
        }

        // check GPSs are within 50m of each other and that blending is healthy
        float distance_m;
        if (!sensor_gps_all_consistent(&distance_m)) {
            mb_arming_check_failed2(ARMING_CHECK_GPS, report, "GPS positions differ by %4.1fm",
                         (double)distance_m);
            return false;
        }
        if (!sensor_gps_blend_health_check()) {
            mb_arming_check_failed2(ARMING_CHECK_GPS, report, "GPS blending unhealthy");
            return false;
        }

        // check AHRS and GPS are within 10m of each other
        const Location *gps_loc = sensor_gps_location();

        Location ahrs_loc;
        if (ahrs_get_location(ahrs, &ahrs_loc)) {
            const float distance = location_get_distance(&ahrs_loc, gps_loc);
            if (distance > AP_ARMING_AHRS_GPS_ERROR_MAX) {
                mb_arming_check_failed2(ARMING_CHECK_GPS, report, "GPS and AHRS differ by %4.1fm", (double)distance);
                return false;
            }
        }
    }

    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) || (_singleton->checks_to_perform & ARMING_CHECK_GPS_CONFIG)) {
        uint8_t first_unconfigured;
        if (sensor_gps_first_unconfigured_gps(&first_unconfigured)) {
            mb_arming_check_failed2(ARMING_CHECK_GPS_CONFIG,
                         report,
                         "GPS %d failing configuration checks",
                         first_unconfigured + 1);
            if (report) {
                sensor_gps_broadcast_first_configuration_failure_reason();
            }
            return false;
        }
    }
#endif

    return true;
}

bool mb_arming_BoardVoltageChecks(bool report)
{
    // check board voltage
    if ((_singleton->checks_to_perform & ARMING_CHECK_ALL) || (_singleton->checks_to_perform & ARMING_CHECK_VOLTAGE)) {
        uitc_power_status board_power_status;

        if (itc_copy_from_hub(ITC_ID(power_status), &board_power_status) != 0) {
            return true;
        }

#if defined(HAL_HAVE_BOARD_VOLTAGE)
        const float bus_voltage =  board_power_status.board_voltage;
        const float vbus_min = brd_get_minimum_board_voltage();
        if(((bus_voltage < vbus_min) || (bus_voltage > AP_ARMING_BOARD_VOLTAGE_MAX))) {
            mb_arming_check_failed2(ARMING_CHECK_VOLTAGE, report, "Board (%1.1fv) out of range %1.1f-%1.1fv", (double)bus_voltage, (double)vbus_min, (double)AP_ARMING_BOARD_VOLTAGE_MAX);
            return false;
        }
#endif // HAL_HAVE_BOARD_VOLTAGE

#if defined(HAL_HAVE_SERVO_VOLTAGE)
       const float vservo_min = brd_get_minimum_servo_voltage();
        if (math_flt_positive(vservo_min)) {
            const float servo_voltage =  board_power_status.servorail_voltage;
            if (servo_voltage < vservo_min) {
                mb_arming_check_failed2(ARMING_CHECK_VOLTAGE, report, "Servo voltage to low (%1.2fv < %1.2fv)", (double)servo_voltage, (double)vservo_min);
                return false;
            }
        }
#endif // HAL_HAVE_SERVO_VOLTAGE
    }

    return true;
}

bool mb_arming_RcCalibrationChecks(bool report)
{
    bool check_passed = true;
    const uint8_t num_channels = rcs_get_valid_channel_count();
    for (uint8_t i = 0; i < NUM_RC_CHANNELS; i++) {
        rc_channel_const_t c = rcs_chan(i);
        if (c == NULL) {
            continue;
        }
        if (i >= num_channels && !(rc_has_override(c))) {
            continue;
        }
        const uint16_t trim = rc_get_radio_trim(c);
        if (rc_get_radio_min(c) > trim) {
            mb_arming_check_failed2(ARMING_CHECK_RC, report, "RC%d_MIN is greater than RC%d_TRIM", i + 1, i + 1);
            check_passed = false;
        }
        if (rc_get_radio_max(c) < trim) {
            mb_arming_check_failed2(ARMING_CHECK_RC, report, "RC%d_MAX is less than RC%d_TRIM", i + 1, i + 1);
            check_passed = false;
        }
    }

    return check_passed;
}

bool mb_arming_MissionChecks(bool report)
{
    if (((_singleton->checks_to_perform & ARMING_CHECK_ALL) || (_singleton->checks_to_perform & ARMING_CHECK_MISSION)) &&
        _singleton->_required_mission_items) {

#if 0   //HAL_RALLY_ENABLED
        AP_Rally *rally = AP::rally();
        if (rally == nullptr) {
            mb_arming_check_failed2(ARMING_CHECK_MISSION, report, "No rally library present");
            return false;
        }
#else
        //mb_arming_check_failed2(ARMING_CHECK_MISSION, report, "No rally library present");
#endif

        const struct MisItemTable {
          enum MIS_ITEM_CHECK check;
          MAV_CMD mis_item_type;
          const char *type;
        } misChecks[] = {
          {ARMING_MIS_ITEM_CHECK_LAND,          MAV_CMD_NAV_LAND,           "land"},
          {ARMING_MIS_ITEM_CHECK_VTOL_LAND,     MAV_CMD_NAV_VTOL_LAND,      "vtol land"},
          {ARMING_MIS_ITEM_CHECK_DO_LAND_START, MAV_CMD_DO_LAND_START,      "do land start"},
          {ARMING_MIS_ITEM_CHECK_TAKEOFF,       MAV_CMD_NAV_TAKEOFF,        "takeoff"},
          {ARMING_MIS_ITEM_CHECK_VTOL_TAKEOFF,  MAV_CMD_NAV_VTOL_TAKEOFF,   "vtol takeoff"},
          {ARMING_MIS_ITEM_CHECK_RETURN_TO_LAUNCH,  MAV_CMD_NAV_RETURN_TO_LAUNCH,   "RTL"},
        };
        for (uint8_t i = 0; i < ARRAY_SIZE(misChecks); i++) {
            if (_singleton->_required_mission_items & misChecks[i].check) {
                if (!mission_contains_item(misChecks[i].mis_item_type)) {
                    mb_arming_check_failed2(ARMING_CHECK_MISSION, report, "Missing mission item: %s", misChecks[i].type);
                    return false;
                }
            }
        }
#if 0   //HAL_RALLY_ENABLED
        if (_required_mission_items & MIS_ITEM_CHECK_RALLY) {
            Location ahrs_loc;
            if (!AP::ahrs().get_location(ahrs_loc)) {
                check_failed(ARMING_CHECK_MISSION, report, "Can't check rally without position");
                return false;
            }
            RallyLocation rally_loc = {};
            if (!rally->find_nearest_rally_point(ahrs_loc, rally_loc)) {
                check_failed(ARMING_CHECK_MISSION, report, "No sufficently close rally point located");
                return false;
            }
          }
#endif

#if !defined(RT_USING_FAL)
        if (mb_arming_check_enabled(ARMING_CHECK_MISSION) &&
            mission_failed_sdcard_storage()) {
            mb_arming_check_failed2(ARMING_CHECK_MISSION, report, "Failed to open %s", AP_MISSION_SDCARD_FILENAME);
        }
#endif
    }

    return true;
}

bool mb_arming_TerrainDatabaseRequired()
{
    if (mission_contains_terrain_alt_items()) {
        return true;
    }
    return false;
}

/*
  check base system operations
 */
bool mb_arming_SystemChecks(bool report)
{
#if 0
    char buffer[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1]  = {0};

    if (mb_arming_check_enabled(ARMING_CHECK_SYSTEM)) {
        if (!hal.storage->healthy()) {
            mb_arming_check_failed2(ARMING_CHECK_SYSTEM, report, "Param storage failed");
            return false;
        }
#if AP_TERRAIN_AVAILABLE
        const AP_Terrain *terrain = AP_Terrain::get_singleton();
        if ((terrain != nullptr) && terrain->init_failed()) {
            mb_arming_check_failed2(ARMING_CHECK_SYSTEM, report, "Terrain out of memory");
            return false;
        }
#endif
#if AP_SCRIPTING_ENABLED
        const AP_Scripting *scripting = AP_Scripting::get_singleton();
        if ((scripting != nullptr) && !scripting->arming_checks(sizeof(buffer), buffer)) {
            mb_arming_check_failed2(ARMING_CHECK_SYSTEM, report, "%s", buffer);
            return false;
        }
#endif
#if HAL_ADSB_ENABLED
        AP_ADSB *adsb = AP::ADSB();
        if ((adsb != nullptr) && adsb->enabled() && adsb->init_failed()) {
            mb_arming_check_failed2(ARMING_CHECK_SYSTEM, report, "ADSB out of memory");
            return false;
        }
#endif
    }
    if (AP::internalerror().errors() != 0) {
        AP::internalerror().errors_as_string((uint8_t*)buffer, ARRAY_SIZE(buffer));
        mb_arming_check_failed(report, "Internal errors 0x%x l:%u %s", (unsigned int)AP::internalerror().errors(), AP::internalerror().last_error_line(), buffer);
        return false;
    }

    if (check_enabled(ARMING_CHECK_PARAMETERS)) {
#if AP_RPM_ENABLED
        auto *rpm = AP::rpm();
        if (rpm && !rpm->arming_checks(sizeof(buffer), buffer)) {
            mb_arming_check_failed2(ARMING_CHECK_PARAMETERS, report, "%s", buffer);
            return false;
        }
#endif
        auto *relay = AP::relay();
        if (relay && !relay->arming_checks(sizeof(buffer), buffer)) {
            mb_arming_check_failed2(ARMING_CHECK_PARAMETERS, report, "%s", buffer);
            return false;
        }
#if HAL_PARACHUTE_ENABLED
        auto *chute = AP::parachute();
        if (chute && !chute->arming_checks(sizeof(buffer), buffer)) {
            mb_arming_check_failed2(ARMING_CHECK_PARAMETERS, report, "%s", buffer);
            return false;
        }
#endif
#if HAL_BUTTON_ENABLED
        const auto &button = AP::button();
        if (!button.arming_checks(sizeof(buffer), buffer)) {
            mb_arming_check_failed2(ARMING_CHECK_PARAMETERS, report, "%s", buffer);
            return false;
        }
#endif
    }
#endif

    return true;
}

// check nothing is too close to vehicle
bool mb_arming_ProximityChecks(bool report)
{
#if 0
#if HAL_PROXIMITY_ENABLED
    const AP_Proximity *proximity = AP::proximity();
    // return true immediately if no sensor present
    if (proximity == nullptr) {
        return true;
    }
    char buffer[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1];
    if (!proximity->prearm_healthy(buffer, ARRAY_SIZE(buffer))) {
        check_failed(report, "%s", buffer);
        return false;
    }
    return true;
#endif
#endif


    return true;
}

void mb_arming_log_write_arm(const bool forced, const enum ArmingMethod method)
{
    const struct log_Arm_Disarm pkt  = {
        APLOG_PACKET_HEADER_INIT(LOG_ARM_DISARM_MSG),
        .time_us                 = time_micros64(),
        .arm_state               = mb_arming_is_armed(),
        .arm_checks              = mb_arming_get_enabled_checks(),
        .forced                  = forced,
        .method                  = (uint8_t)method,
    };
    blog_write_critical_block(&pkt, sizeof(pkt));
    aplog_write_event(LOG_EVENT_ARMED);
}

void mb_arming_log_write_disarm(const bool forced, const enum ArmingMethod method)
{
    const struct log_Arm_Disarm pkt = {
        APLOG_PACKET_HEADER_INIT(LOG_ARM_DISARM_MSG),
        .time_us                 = time_micros64(),
        .arm_state               = mb_arming_is_armed(),
        .arm_checks              = 0,
        .forced                  = forced,
        .method                  = (uint8_t)method
    };
    blog_write_critical_block(&pkt, sizeof(pkt));
    aplog_write_event(LOG_EVENT_DISARMED);
}


/// ops
// bool do_arming_checks=true
bool mb_arming_arm(enum ArmingMethod method, bool do_arming_checks)
{
    if (_singleton->ops != NULL && _singleton->ops->arm != NULL) {
        return _singleton->ops->arm(method, do_arming_checks);
    }

    return mb_armingArm(method, do_arming_checks);
}

// bool do_disarm_checks=true
bool mb_arming_disarm(enum ArmingMethod method, bool do_disarm_checks)
{
    if (_singleton->ops != NULL && _singleton->ops->disarm != NULL) {
        return _singleton->ops->disarm(method, do_disarm_checks);
    }

    return mb_armingDisarm(method, do_disarm_checks);
}

// pre_arm_checks() is virtual so it can be modified in a vehicle specific subclass
bool mb_arming_pre_arm_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->pre_arm_checks != NULL) {
        return _singleton->ops->pre_arm_checks(report);
    }

    return mb_arming_PreArmChecks(report);
}

// some arming checks have side-effects, or require some form of state
// change to have occurred, and thus should not be done as pre-arm
// checks.  Those go here:
bool mb_arming_arm_checks(enum ArmingMethod method)
{
    if (_singleton->ops != NULL && _singleton->ops->arm_checks != NULL) {
        return _singleton->ops->arm_checks(method);
    }

    return mb_arming_ArmChecks(method);
}

bool mb_arming_barometer_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->barometer_checks != NULL) {
        return _singleton->ops->barometer_checks(report);
    }

    return mb_arming_BarometerChecks(report);
}

bool mb_arming_ins_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->ins_checks != NULL) {
        return _singleton->ops->ins_checks(report);
    }

    return mb_arming_InsChecks(report);
}

bool mb_arming_gps_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->gps_checks != NULL) {
        return _singleton->ops->gps_checks(report);
    }

    return mb_arming_GpsChecks(report);
}

bool mb_arming_board_voltage_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->board_voltage_checks != NULL) {
        return _singleton->ops->board_voltage_checks(report);
    }

    return mb_arming_BoardVoltageChecks(report);
}

bool mb_arming_rc_calibration_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->rc_calibration_checks != NULL) {
        return _singleton->ops->rc_calibration_checks(report);
    }

    return mb_arming_RcCalibrationChecks(report);
}

bool mb_arming_mission_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->mission_checks != NULL) {
        return _singleton->ops->mission_checks(report);
    }

    return mb_arming_MissionChecks(report);
}

// expected to return true if the terrain database is required to have
// all data loaded
bool mb_arming_terrain_database_required()
{
    if (_singleton->ops != NULL && _singleton->ops->terrain_database_required != NULL) {
        return _singleton->ops->terrain_database_required();
    }

    return mb_arming_TerrainDatabaseRequired();
}

bool mb_arming_system_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->system_checks != NULL) {
        return _singleton->ops->system_checks(report);
    }

    return mb_arming_SystemChecks(report);
}

bool mb_arming_proximity_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->proximity_checks != NULL) {
        return _singleton->ops->proximity_checks(report);
    }

    return mb_arming_ProximityChecks(report);
}

// mandatory checks that cannot be bypassed.  This function will only be called if ARMING_CHECK is zero or arming forced
bool mb_arming_mandatory_checks(bool report)
{
    if (_singleton->ops != NULL && _singleton->ops->mandatory_checks != NULL) {
        return _singleton->ops->mandatory_checks(report);
    }

    return mb_arming_MandatoryChecks(report);
}

/*------------------------------------test------------------------------------*/


